﻿using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    public class CssEditBase : TextEditor
    {
        public CssEditBase()
        {
            this.SnapsToDevicePixels = true;//避免折叠线模糊
            this.Background = Brushes.WhiteSmoke;
            RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);

            this.WordWrap = false;  //CSS 行一般不会太长

            this.PreviewKeyDown += CssEditBase_PreviewKeyDown;


            #region 代码折叠处理Folding
            foldingStrategy = new CssFoldingStrategy(this);
            foldingManager = CustomFoldingManager.Install(this.TextArea);
            foldingStrategy.UpdateFoldings(FoldingManager, this.Document);

            foldingUpdateTimer.Interval = TimeSpan.FromSeconds(0.05);//50毫秒。
            foldingUpdateTimer.Tick += foldingUpdateTimer_Tick;
            foldingUpdateTimer.Start();
            #endregion
        }
        
        #region 代码折叠
        void foldingUpdateTimer_Tick(object sender, EventArgs e)
        {
            if (foldingStrategy != null)
            {
                foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
            }
        }

        DispatcherTimer foldingUpdateTimer = new DispatcherTimer();

        /// <summary>
        /// 提供这个属性是为了便于在选项卡“不在最前”时停止折叠计算，减少消耗。
        /// </summary>
        public DispatcherTimer FoldingUpdateTimer { get { return this.foldingUpdateTimer; } }

        public CustomFoldingManager FoldingManager
        {
            get { return foldingManager; }
        }

        CustomFoldingManager foldingManager;
        AbstractFoldingStrategy foldingStrategy;

        public AbstractFoldingStrategy FoldingStrategy
        {
            get { return foldingStrategy; }
        }
        #endregion

        private void CssEditBase_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            bool isCtrl = false ||
                (Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) > 0;

            bool isShift = false ||
                (Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0;

            bool isAlt = false ||
                (Keyboard.GetKeyStates(Key.RightAlt) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftAlt) & KeyStates.Down) > 0;

            try
            {
                switch (e.Key)
                {
                    case Key.I://为什么在这里处理？因为avalonEdit已经绑死Alt+Up，使用Alt+Up不起任何作用。其实用覆盖 OnKeyDown() 方法的办法可以解决。
                        {
                            if (isCtrl)//移动到上一行的上方。
                            {
                                if (isShift)
                                {
                                    if (!isAlt) SwapWithPreviewLine();
                                }
                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.K://为什么用这个？因为avalonEdit已经绑死Alt+Up，使用Alt+Up不起任何作用。其实用覆盖 OnKeyDown() 方法的办法可以解决。
                        {
                            if (isCtrl && isShift && !isAlt)//移动到下一行的下方。
                            {
                                SwapWithNextLine();

                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.Back://BackSpace，退格键
                        {
                            if (isShift && !isCtrl && !isAlt)
                            {
                                //删除到行首
                                var textArea = this.TextArea;
                                if (textArea != null && textArea.IsKeyboardFocused)
                                {
                                    var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                                    var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);

                                    var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);

                                    this.Select(fstSelectedLine.Offset, textArea.Selection.Length + (this.SelectionStart - fstSelectedLine.Offset));
                                    textArea.PerformTextInput("");

                                    e.Handled = true;
                                }
                            }
                            break;
                        }
                    case Key.Enter://回车键
                        {
                            if (isCtrl)
                            {
                                if (!isShift)
                                {
                                    //在当前行下方插入一个新行，并移动光标。
                                    AppendANewLine();
                                    e.Handled = true;
                                }
                            }
                            else
                            {
                                if (isShift)
                                {
                                    //在当前行上方插入一个新行，并移动光标。
                                    InsertANewLine();
                                    e.Handled = true;
                                }

                                //AvalonEdit有个默认行为：若一行文本以空格开头，无论在第一个非空格字符前按下回车，还是在这些空格前按下回车，
                                //新行开头都不会保留空格！！！
                            }
                            break;
                        }
                    case Key.Tab://Tab键
                        {
                            if (!isCtrl && !isAlt)
                            {
                                if (isShift)
                                {
                                    if (JumpOverMarks(ref e, isShift)) return;
                                }
                                else
                                {
                                    //跳过反引号，用在：按Ctrl+`输入一对反引号并将插入点置于两个反引号之间，输入一些文本后直接跳过右反引号继续输入其它文本。
                                    //这比按两个组合方向按键跳过去或者按方向键跳过去更方便。
                                    //*号对与_对情形类似。

                                    if (JumpOverMarks(ref e, isShift)) return;
                                }
                            }
                            break;
                        }
                    case Key.Z:
                        {
                            if (!isAlt)
                            {
                                if (!isShift && isCtrl)
                                {
                                    if (this.CanUndo) this.Undo();
                                    e.Handled = true;
                                }
                            }
                            break;
                        }
                    case Key.Y:
                        {
                            if (!isAlt && !isShift && isCtrl)
                            {
                                if (this.CanRedo) this.Redo();
                                e.Handled = true;
                            }
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show("发生意外错误，无法执行操作。建议保存文件并重启程序。\r\n" +
                    ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }


        /// <summary>
        /// 附加新行。
        /// </summary>
        private void AppendANewLine()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var text = this.Document.GetText(curLine.Offset, curLine.Length);

            this.Document.Insert(curLine.EndOffset, "\r\n");
            this.Select(curLine.EndOffset + 2, 0);
        }

        /// <summary>
        /// 插入新行。
        /// </summary>
        private void InsertANewLine()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var text = this.Document.GetText(curLine.Offset, curLine.Length);

            this.Document.Insert(curLine.Offset, "\r\n");
            this.Select(curLine.Offset, 0);
        }

        /// <summary>
        /// 根据选项构建正则表达式。
        /// </summary>
        /// <param name="textToFind">要查找的文本</param>
        /// <param name="leftToRight">正序还是逆序。</param>
        private Regex GetRegEx(string textToFind, bool leftToRight)
        {
            RegexOptions options = RegexOptions.None;
            if (leftToRight)
            {
                options |= RegexOptions.RightToLeft;
            }
            options |= RegexOptions.IgnoreCase;

            string pattern = textToFind; // Regex.Escape(textToFind);
            return new Regex(pattern, options);
        }

        /// <summary>
        /// 跳转到下一个输入区域。这些输入区域包括：成对的小括弧之间，成对的大括弧之间，成对的中括弧之间等等。
        /// </summary>
        /// <param name="e"></param>
        private bool JumpOverMarks(ref KeyEventArgs e, bool isShift)
        {
            if (SelectionLength != 0) return false;
            //只有当插入点正好在某些特殊符号（及符号组合）前面时才起作用，用来跳过这些个特殊符号组合。

            try
            {
                var regexText = "(</?[a-zA-Z]{1,}( [^><]*)?>)|(\\*\\*)|(\\[\\=)|(\\=\\])|(__)|([_\\*“”‘’'\"`<>《》〈〉\\(\\)\\[\\]\\{\\}（）〔〕〈〉「」『』〖〗【】［］｛｝＂＇°])";

                Regex regex = GetRegEx(regexText, isShift);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? SelectionStart : SelectionStart + SelectionLength;
                Match match = regex.Match(Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(Text, Text.Length);
                    else
                        match = regex.Match(Text, 0);
                }

                if (match.Success)
                {
                    if (isShift)
                    {
                        if (match.Index + match.Length != SelectionStart) return false;

                        var destSel = match.Index;
                        if (destSel >= 0)
                        {
                            Select(destSel, 0);
                            TextLocation loc = Document.GetLocation(match.Index);
                            ScrollTo(loc.Line, loc.Column);

                            e.Handled = true;
                            return true;
                        }
                    }
                    else
                    {
                        if (match.Index != SelectionStart + SelectionLength) return false;

                        var destSel = match.Index + match.Length;
                        if (destSel <= Document.TextLength)
                        {
                            Select(destSel, 0);
                            TextLocation loc = Document.GetLocation(match.Index);
                            ScrollTo(loc.Line, loc.Column);

                            e.Handled = true;
                            return true;
                        }
                    }

                    return false;
                }

                return false;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// [覆盖方法]只有用这个办法才能使Alt+Up/Down生效。
        /// </summary>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyboardDevice.Modifiers == ModifierKeys.Alt
                && (e.KeyboardDevice.IsKeyDown(Key.RightAlt) || e.KeyboardDevice.IsKeyDown(Key.LeftAlt)))
            {
                if ((e.Key == Key.Up || e.SystemKey == Key.Up))
                {
                    SwapWithPreviewLine();
                    e.Handled = true;
                }
                else if ((e.Key == Key.Down || e.SystemKey == Key.Down))
                {
                    SwapWithNextLine();
                    e.Handled = true;
                }
            }
            base.OnKeyDown(e);
        }

        /// <summary>
        /// 与后一行交换位置。
        /// </summary>
        internal void SwapWithNextLine()
        {
            var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
            var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var nextLine = curEndLine.NextLine;

            if (nextLine != null)
            {
                this.BeginChange();
                var lineSplitter = "\n";
                if (this.Document.Text.Contains("\r\n"))
                {
                    lineSplitter = "\r\n";
                }
                var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                this.Document.Remove(nextLine.Offset - lineSplitter.Length, nextLine.Length + lineSplitter.Length);//先删除下一行

                var curStartLineOffset = curStartLine.Offset;
                this.Document.Insert(curStartLineOffset, nextLineText + lineSplitter);
                this.EndChange();
            }
        }

        /// <summary>
        /// 与前一行交换位置。
        /// </summary>
        internal void SwapWithPreviewLine()
        {
            var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
            var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var preLine = curStartLine.PreviousLine;

            if (preLine != null)
            {
                this.BeginChange();
                var preStartLineOffset = preLine.Offset;
                var startOffset = this.SelectionStart - curStartLine.Offset;
                var selLength = this.SelectionLength;

                var lineSplitter = "\n";
                if (this.Document.Text.Contains("\r\n"))
                {
                    lineSplitter = "\r\n";
                }

                var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                this.Document.Insert(curEndLine.EndOffset, lineSplitter);
                this.Document.Insert(curEndLine.EndOffset + lineSplitter.Length, preLineText);
                this.Document.Remove(preLine.Offset, preLine.Length + lineSplitter.Length);
                this.EndChange();

                this.Select(preStartLineOffset + startOffset, selLength);
            }
        }

    }
}
